package com.flow.framework.api.doc.plugins;

import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Optional;
import com.flow.framework.core.response.Response;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.ResponseHeader;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import springfox.documentation.builders.ResponseMessageBuilder;
import springfox.documentation.schema.ModelReference;
import springfox.documentation.schema.TypeNameExtractor;
import springfox.documentation.service.Header;
import springfox.documentation.service.ResponseMessage;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.contexts.ModelContext;
import springfox.documentation.spi.service.OperationBuilderPlugin;
import springfox.documentation.spi.service.contexts.OperationContext;

import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.google.common.base.Optional.fromNullable;
import static com.google.common.collect.Maps.newHashMap;
import static com.google.common.collect.Sets.newHashSet;
import static springfox.documentation.schema.ResolvedTypes.modelRefFactory;
import static springfox.documentation.spi.schema.contexts.ModelContext.returnValue;
import static springfox.documentation.spring.web.readers.operation.ResponseMessagesReader.httpStatusCode;
import static springfox.documentation.spring.web.readers.operation.ResponseMessagesReader.message;
import static springfox.documentation.swagger.annotations.Annotations.resolvedTypeFromOperation;
import static springfox.documentation.swagger.annotations.Annotations.resolvedTypeFromResponse;
import static springfox.documentation.swagger.readers.operation.ResponseHeaders.headers;
import static springfox.documentation.swagger.readers.operation.ResponseHeaders.responseHeaders;

/**
 * 响应消息解析器 参考springfox.documentation.swagger.readers.operation.SwaggerResponseMessageReader
 *
 * @author luoguopiao
 * @version 0.0.1
 * @date 2023/2/12
 */
@RequiredArgsConstructor
public class CustomizationResponseMessageReader implements OperationBuilderPlugin {

    private static final String VOID = "void";

    private final TypeNameExtractor typeNameExtractor;
    private final TypeResolver typeResolver;

    @Override
    public void apply(OperationContext context) {
        context.operationBuilder()
                .responseMessages(read(context));
    }

    @Override
    public boolean supports(DocumentationType delimiter) {
        return true;
    }

    protected Set<ResponseMessage> read(OperationContext context) {
        ResolvedType defaultResponse = context.getReturnType();
        defaultResponse = context.alternateFor(defaultResponse);
        TypeResolver resolver = new TypeResolver();

        // 如果直接对外的controller响应（不包含rpc的）不是框架响应，则使用框架响应对controller响应进行包装
        if (!defaultResponse.isInstanceOf(Response.class)) {
            defaultResponse = resolver.resolve(Response.class, defaultResponse);
        }
        Optional<ApiOperation> operationAnnotation = context.findAnnotation(ApiOperation.class);
        Optional<ResolvedType> operationResponse =
                operationAnnotation.transform(resolvedTypeFromOperation(typeResolver, defaultResponse));
        Optional<ResponseHeader[]> defaultResponseHeaders = operationAnnotation.transform(responseHeaders());
        Map<String, Header> defaultHeaders = newHashMap();
        if (defaultResponseHeaders.isPresent()) {
            defaultHeaders.putAll(headers(defaultResponseHeaders.get()));
        }

        List<ApiResponses> allApiResponses = context.findAllAnnotations(ApiResponses.class);
        Set<ResponseMessage> responseMessages = newHashSet();

        Map<Integer, ApiResponse> seenResponsesByCode = newHashMap();
        for (ApiResponses apiResponses : allApiResponses) {
            ApiResponse[] apiResponseAnnotations = apiResponses.value();
            for (ApiResponse apiResponse : apiResponseAnnotations) {
                if (!seenResponsesByCode.containsKey(apiResponse.code())) {
                    seenResponsesByCode.put(apiResponse.code(), apiResponse);
                    ModelContext modelContext = returnValue(
                            context.getGroupName(), apiResponse.response(),
                            context.getDocumentationType(),
                            context.getAlternateTypeProvider(),
                            context.getGenericsNamingStrategy(),
                            context.getIgnorableParameterTypes());
                    Optional<ModelReference> responseModel = Optional.absent();
                    Optional<ResolvedType> type = resolvedType(null, apiResponse);
                    if (isSuccessful(apiResponse.code())) {
                        type = type.or(operationResponse);
                    }
                    if (type.isPresent()) {
                        responseModel = Optional.of(
                                modelRefFactory(modelContext, typeNameExtractor)
                                        .apply(context.alternateFor(type.get()))
                        );
                    }
                    Map<String, Header> headers = newHashMap(defaultHeaders);
                    headers.putAll(headers(apiResponse.responseHeaders()));

                    responseMessages.add(new ResponseMessageBuilder().code(apiResponse.code()).message(apiResponse.message())
                            .responseModel(responseModel.orNull()).headersWithDescription(headers).build());
                }
            }
        }
        ModelContext modelContext;
        if (operationResponse.isPresent()) {
            modelContext = returnValue(
                    context.getGroupName(),
                    operationResponse.get(),
                    context.getDocumentationType(),
                    context.getAlternateTypeProvider(),
                    context.getGenericsNamingStrategy(),
                    context.getIgnorableParameterTypes());
            defaultResponse = operationResponse.get();
        } else {
            modelContext = returnValue(
                    context.getGroupName(),
                    defaultResponse,
                    context.getDocumentationType(),
                    context.getAlternateTypeProvider(),
                    context.getGenericsNamingStrategy(),
                    context.getIgnorableParameterTypes());
        }

        ModelReference responseModel = modelRefFactory(modelContext, typeNameExtractor).apply(defaultResponse);
        context.operationBuilder().responseModel(responseModel);
        ResponseMessage defaultMessage = new ResponseMessageBuilder()
                .code(httpStatusCode(context))
                .message(message(context))
                .responseModel(responseModel)
                .build();
        if (!responseMessages.contains(defaultMessage) && !VOID.equals(responseModel.getType())) {
            responseMessages.add(defaultMessage);
        }
        return responseMessages;
    }


    static boolean isSuccessful(int code) {
        try {
            return HttpStatus.Series.SUCCESSFUL.equals(HttpStatus.Series.valueOf(code));
        } catch (Exception ignored) {
            return false;
        }
    }

    private Optional<ResolvedType> resolvedType(
            ResolvedType resolvedType,
            ApiResponse apiResponse) {
        return fromNullable(resolvedTypeFromResponse(
                typeResolver,
                resolvedType).apply(apiResponse));
    }
}
